6.3 - Querying the Game - Checking for Pathing and Abilities
Your bot's standard senses (self.state
and its helpers) provide a snapshot of what is. But to make truly intelligent decisions, your bot needs to ask what could be. Can a unit get from A to B? Is an ability actually ready to use? Answering these questions requires querying the game engine for a specific calculation.
Queries are async
functions that send a request to the StarCraft II engine and await
a response. They are essential tools for moving beyond simple reactive logic to proactive, error-proof planning.
The Query System
Think of a query as a direct question to the game engine's internal rulebook.
+------------------+
| | 1. "Can a Marine walk from my base to this island?"
| Your Python Bot | --------------------------------------------------> +---------------+
| (The Querier) | | |
| | 2. "No, the path distance is None." | SC2 Game |
| | <-------------------------------------------------- | Engine |
+------------------+ | (The Oracle) |
+---------------+
This request-response cycle allows your bot to validate its plans before committing resources.
The Three Essential Queries
Query | Key Function | What It Asks | When to Use It |
---|---|---|---|
Placement | await self.find_placement(...) | "Where is a valid spot to build this structure?" | Always. Before every self.build command to ensure the location is legal. |
Pathing | await self.client.query_pathing(...) | "Is there a walkable ground path from point A to point B?" | Before sending a ground unit to a distant location, especially a potential expansion, to avoid "island" traps. |
Ability | await self.get_available_abilities(...) | "Which abilities can this specific unit use right now?" | Always. Before casting any ability with a cooldown to ensure it's not on cooldown. This is more reliable than just checking energy. |
A Developer's Checklist for Using Queries
- 1. Identify the Question: What do I need to know before I act? (e.g., "Is this expansion on an island?")
- 2. Choose the Right Query: Select the function that answers your question (
query_pathing
,get_available_abilities
, etc.). - 3.
await
the Response: Since queries are network requests, you mustawait
the result. - 4. Handle the Response: The query might return
None
or an empty list if the answer is negative. Your code must handle this gracefully.if path_distance is None:
if AbilityId.STIMPACK in available_abilities:
- 5. Act on the Verified Information: Only issue your command after the query has confirmed it's a valid action.
Code Example: The "Inquisitive" Bot
This bot demonstrates how to use queries to make its decisions more robust. It will use a pathing query to verify that its next expansion is reachable and an ability query to ensure its Stimpack command is valid.
# inquisitive_bot.py
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.ability_id import AbilityId
class InquisitiveBot(BotAI):
"""A bot that uses queries to make smarter decisions."""
async def on_step(self, iteration: int):
await self.manage_expansion()
await self.manage_stimpack()
# Helper to build an army for the demo.
await self.build_army()
async def manage_expansion(self):
"""Uses a pathing query to safely expand."""
# Only try to expand if we have 1 base and can afford it.
if len(self.townhalls) == 1 and self.can_afford(UnitTypeId.COMMANDCENTER):
# Find the location of the next expansion spot.
next_expansion_location = await self.get_next_expansion()
if not next_expansion_location:
return
# QUERY: Can a worker walk from our start to the expansion?
path_distance = await self.client.query_pathing(self.start_location, next_expansion_location)
# HANDLE RESPONSE:
if path_distance is None:
# The path is blocked (e.g., an island). Do not expand.
print(f"WARNING: Expansion at {next_expansion_location} is unreachable by ground.")
else:
# The path is clear. Proceed with the expansion.
print(f"INFO: Expansion is reachable (Distance: {path_distance:.2f}). Expanding.")
await self.expand_now()
async def manage_stimpack(self):
"""Uses an ability query to safely use Stimpack."""
# Find marines near enemies.
marines_in_danger = self.units(UnitTypeId.MARINE).filter(
lambda m: self.enemy_units.closer_than(10, m).exists
)
for marine in marines_in_danger:
# QUERY: What abilities can this specific marine use right now?
available_abilities = await self.get_available_abilities(marine)
# HANDLE RESPONSE: Check if Stimpack is in the list.
if AbilityId.STIMPACK in available_abilities:
print(f"INFO: Marine {marine.tag} is using Stimpack.")
marine(AbilityId.STIMPACK)
async def build_army(self):
"""A simple helper method to produce units for the demo."""
if self.supply_left < 2 and not self.already_pending(UnitTypeId.SUPPLYDEPOT):
if self.can_afford(UnitTypeId.SUPPLYDEPOT):
await self.build(UnitTypeId.SUPPLYDEPOT, near=self.start_location.towards(self.game_info.map_center, 5))
if not self.structures(UnitTypeId.BARRACKS).exists:
if self.can_afford(UnitTypeId.BARRACKS):
await self.build(UnitTypeId.BARRACKS, near=self.start_location.towards(self.game_info.map_center, 8))
elif self.structures(UnitTypeId.BARRACKS).ready.idle.exists and self.can_afford(UnitTypeId.MARINE):
self.train(UnitTypeId.MARINE)
if __name__ == "__main__":
run_game(
maps.get("BlackburnAIE"),
[
Bot(Race.Terran, InquisitiveBot()),
Computer(Race.Zerg, Difficulty.Medium)
],
realtime=True,
)